use std::sync::Arc;

use arc_swap::ArcSwap;
use derive_more::{Constructor, Debug};
use parking_lot::RwLock;

pub type AtomicArcOnSetFn<T> = Box<dyn Fn(Arc<T>, Arc<T>) + Send + Sync>;

#[derive(Debug, Default, Constructor)]
pub struct AtomicArc<T> {
    value: ArcSwap<T>,
    #[debug(skip)]
    on_set: RwLock<Option<AtomicArcOnSetFn<T>>>,
}

impl<T> AtomicArc<T> {
    #[inline]
    pub fn from_value(value: T) -> Self {
        Self::new(ArcSwap::from_pointee(value), None.into())
    }

    #[inline]
    pub fn get(&self) -> Arc<T> {
        self.value.load_full()
    }

    #[inline]
    pub fn on_set<F>(&self, op: F)
    where
        F: Fn(Arc<T>, Arc<T>) + Send + Sync + 'static,
    {
        *self.on_set.write() = Some(Box::new(op))
    }

    #[inline]
    pub fn set(&self, value: T)
    where
        T: PartialEq,
    {
        let new_value = Arc::new(value);
        if self.get() != new_value {
            let old_value = self.value.swap(new_value.clone());
            if let Some(op) = &*self.on_set.read() {
                op(old_value, new_value)
            }
        }
    }
}
